#!/usr/bin/python
##########################################################################
#
# accel.py - Handling accelerometer events of an Intel Classmate Netbook
# The script is inspired on Kay Hannay <klinux@hannay.de> work
#
# Copyright (C) 2012  Pilolli Pietro <alpha@paranoici.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##########################################################################

import sys
import os
import math
import time
import ctypes
import struct
import os.path
import fcntl


# Extracted from <asm-generic/ioctl.h>.

# The following is for compatibility across the various Linux
# platforms.  The generic ioctl numbering scheme doesn't really enforce
# a type field.  De facto, however, the top 8 bits of the lower 16
# bits are indeed used as a type field, so we might just as well make
# this explicit here.  Please be sure to use the decoding macros
# below from now on.

_IOC_NRBITS   =  8
_IOC_TYPEBITS =  8

# Let any architecture override either of the following before
# including this file.

_IOC_SIZEBITS = 14
_IOC_NRSHIFT = 0
_IOC_TYPESHIFT =(_IOC_NRSHIFT+_IOC_NRBITS)
_IOC_SIZESHIFT =(_IOC_TYPESHIFT+_IOC_TYPEBITS)
_IOC_DIRSHIFT  =(_IOC_SIZESHIFT+_IOC_SIZEBITS)

# Direction bits, which any architecture can choose to override
# before including this file.
_IOC_WRITE = 1
_IOC_READ = 2

# Extracted from <include/linux/input.h>.

# Software event type.
EV_SW = 5

# Tablet mode.
SW_TABLET_MODE = 1

# Absolute accelerometer axis constants.
ABS_X = 0
ABS_Y = 1
ABS_Z = 2

# Local def.
INT6='iiiiii'


def _IOC(direction,type,nr,size):
    return (((direction)  << _IOC_DIRSHIFT) |
             ((type) << _IOC_TYPESHIFT) |
	     ((nr)   << _IOC_NRSHIFT) |
	     ((size) << _IOC_SIZESHIFT))


# Used to create numbers. 
def _IOR(type_, nr, size):
    return _IOC(_IOC_READ, type_, nr, size)


# Get all software switch states.
def EVIOCGSW(len):
    return _IOC(_IOC_READ, 69, 0x1b, len)


# Get the device name.
def EVIOCGNAME(length):
    return (_IOC_READ<<_IOC_DIRSHIFT)|(length<<_IOC_SIZESHIFT)|(0x45<<_IOC_TYPESHIFT)|(0x06<<_IOC_NRSHIFT)


# Get absolute value/limits.
def EVIOCGABS(abs):
    return _IOR(69, 0x40 + abs, 40)

# Unblank the screen.
def unblank_screen():
    xset_command = 'xset dpms force on'
    os.system(xset_command)


# Rectify the touchscreen axis matrix.
def rectify_touchscreen(orientation):
    property='\'Coordinate Transformation Matrix\''
    head = 'xinput set-prop \'' + touchscreen_device + '\' ' + property
    if orientation == "inverted":
        matrix='-1 0 1 0 -1 1 0 0 1'
    elif orientation == "normal":
        matrix = '1 0 0 0 1 0 0 0 1'
    elif orientation == "left":
        matrix = '0 -1 1 1 0 0 0 0 1'
    elif orientation == "right":
        matrix = '0 1 0 -1 0 1 0 0 1'
    xinput_command = head + ' ' + matrix
    print xinput_command
    os.system(xinput_command)


# Set screen orientation.
def set_orientation(orientation):
    # Rotate the screen.scree
    xrandr_command = 'xrandr -o' + orientation
    os.system('xrandr -o ' + orientation)


# Rotate the screen respecting its orientation.
def rotate_screen(orientation):
    print "Rotate " + orientation

    unblank_screen()
    rectify_touchscreen(orientation)
    set_orientation(orientation)

    return orientation


# Get abs value.
def get_abs(fdacc, code):
    buf = struct.pack(INT6, 0, 0, 0, 0, 0, 0)
    abs = fcntl.ioctl(fdacc, EVIOCGABS(code), buf)
    (val,min,max,fuzz,flat,resolution) = struct.unpack(INT6, abs)
    return val


# Autorotate the screen when the pc is in tablet mode 
# in the right position depending on accel data.
def autorotate_tablet(fdacc, orientation):
   axis_x = get_abs(fdacc, ABS_X)
   axis_y = get_abs(fdacc, ABS_Y)
   axis_z = get_abs(fdacc, ABS_Z)
   #print str(axis_x) + " " + str(axis_y) + " " + str(axis_z)
   threshold = 180 
   if(axis_x < (-1*threshold)):
       if (orientation != "right"):
           orientation = rotate_screen("right")
   elif(axis_x > threshold):
       if (orientation != "left"):
           orientation = rotate_screen("left")
   elif(axis_y > -60):
       if (orientation != "inverted"):
           orientation = rotate_screen("inverted")
   elif(axis_x < (threshold/3) ) and (axis_x > (-1*threshold/3)):
       if (orientation != "normal"):
           orientation = rotate_screen("normal")

   return orientation


# Handle the cmpc_tablet event and discriminate
# if the pc is in tablet mode (tablet_mode=True) 
# or in laptop mode (tablet_mode=False).
def handle_tablet_mode_event(fdtab, tablet_mode):
    # Read first byte from evdev.
    input = fdtab.read(8)

    ev_type = struct.unpack("H", input[0:2])[0]
    ev_code = struct.unpack("H", input[2:4])[0]
    ev_val  = struct.unpack("H", input[4:6])[0]

    if ev_type == EV_SW:
        if ev_code == SW_TABLET_MODE:

            if ev_val:
                print  "TABLET MODE"
                orientation = rotate_screen("inverted")
                tablet_mode = True
            else:
                print "LAPTOP MODE"
                orientation = rotate_screen("normal")
                tablet_mode = False

    return tablet_mode


# Setup the file descriptor in order to enable non blocking read.
def setnonblock(fd):
    fl = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)


# Get the tablet mode;
# It returns True if the computer is in tablet mode,
# it returns False otherwise.
def get_tablet_mode():
    tablet_mode = False
    size = struct.calcsize('i')
    buf = size*'\x00'

    test = fcntl.ioctl(fdtab, EVIOCGSW(size), buf)
    values = struct.unpack("i", test)
    bit = values[0]
    if bit != 0:
        tablet_mode = True

    return tablet_mode


# Open the input event device called by name;
# it returns the file descriptor opened in nonblocking mode.
def open_input_device(device_name):

    devices = os.listdir("/dev/input/")
    for evdev in devices:
        try:
            buffer = "\0"*512
            file_name = "/dev/input/" + evdev
	    fd = open(file_name, "r")
            setnonblock(fd)
	    name = fcntl.ioctl(fd, EVIOCGNAME(256), buffer)
            name = name[:name.find("\0")]
            # Return the file descriptor if tablet is found.
            if(name == device_name):
                return fd
            else:
                fd.close()

        except (IOError, OSError), err:
	    continue

    return None


# Open tablet device.
def open_tablet_device():
    # Device name for classmate tablet mode.
    tablet="cmpc_tablet"

    fdtab = open_input_device(tablet)
    if (fdtab == None):
        print "No tablet device found. Exiting..."
        sys.exit(1)
    return fdtab


# Found accelerometer device name.
def found_accelerometer_device_name():
    # Accelerometer device list containing names of supported accelerometer devices.
    accelerometer_list= list()

    # Device name for old classmate accelerometer.
    cmpc_acc="cmpc_accel"
    accelerometer_list.append(cmpc_acc)

    # Device name for new classmate accelerometer.
    cmpc_acc4="cmpc_accel_v4"
    accelerometer_list.append(cmpc_acc4)

    fdaccelerometer = None
    for accelerometer in accelerometer_list:
        fdaccelerometer = open_input_device(accelerometer)
        if (fdaccelerometer != None):
            print "Found accelerometer \'" + accelerometer + "\'"
            fdaccelerometer.close()
            return accelerometer

    print "No accelerometer device " + accelerometer + "found. Exiting..."
    sys.exit(1)
    return None


# Open accelerometer device.
def open_accelerometer_device(acc_devname):
    if (acc_devname != None):
        fdaccelerometer = open_input_device(acc_devname)
        return fdaccelerometer
    return None


def found_touchscreen_device():
    # Touchscreen device list containing names of supported touchscreen devices.
    touchscreen_list = list()
    
    # For taiwaneese classmate.
    ntrig='N-Trig MultiTouch'
    touchscreen_list.append(ntrig)
    
    # For portuguese version.
    touchhid='HID TOUCH HID Touch Panel'
    touchscreen_list.append(touchhid)
    
    eturbotouch='eTurboTouch eTurboTouch'
    touchscreen_list.append(eturbotouch)

    fdtouchscreen = None
    for touchscreen in touchscreen_list:
        fdtouchscreen = open_input_device(touchscreen)
        if (fdtouchscreen != None):
            print "Found touchscreen \'" + touchscreen + "\'"
            fdtouchscreen.close()
            return touchscreen

    print "No touchscreen found"
    return None

# Main.
orientation = "normal"
touchscreen_device = found_touchscreen_device()

# The file descriptor for the tablet device.
fdtab = open_tablet_device()

# Get the initial tablet mode state.
tablet_mode = get_tablet_mode()

accelerometer_device_name = found_accelerometer_device_name()
if tablet_mode:
    fdacc = open_accelerometer_device(accelerometer_device_name)
else:
    fdacc = None
    orientation = "inverted"

while True:
    time.sleep(1)
    # The main loop.
    # If tablet mode autorotate the screen in the right position respecting the accel data.
    if tablet_mode:
        orientation = autorotate_tablet(fdacc, orientation)
   
    try:
        # If a tablet mode event has been found then update the tablet mode status.
        tablet_mode = handle_tablet_mode_event(fdtab, tablet_mode)
        if tablet_mode:
            fdacc = open_accelerometer_device(accelerometer_device_name)
        else:
            if (fdacc!=None):
                fdacc.close()     
                fdacc=None
    except IOError:
        continue 
